import type { registryItemFileSchema } from 'shadcn-vue/schema'
import type { z } from 'zod'
import { promises as fs } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'

// import { Project, ScriptKind } from 'ts-morph'

import { registryItemSchema } from 'shadcn-vue/schema'
import { blockMeta } from '@/registry/registry-block-meta'
import { fixImport } from '~/lib/registry-utils'
import { Index } from '~/registry/__index__'

export function getRegistryComponent(name: string) {
  return Index[name]?.component
}

export async function getRegistryItem(name: string) {
  const item = Index[name]
  if (!item) {
    return null
  }

  // Convert all file paths to object.
  // TODO: remove when we migrate to new registry.
  item.files = item.files.map((file: unknown) =>
    typeof file === 'string' ? { path: file } : file,
  )

  // Fail early before doing expensive file operations.
  const result = registryItemSchema.safeParse(item)
  if (!result.success) {
    return null
  }

  let files: typeof result.data.files = []
  for (const file of item.files) {
    const content = await getFileContent(file)
    const relativePath = path.relative(process.cwd(), file.path)

    files.push({
      ...file,
      path: relativePath,
      content,
    })
  }

  // Fix file paths.
  files = fixFilePaths(files)
  const meta = blockMeta[name]
  const parsed = registryItemSchema.safeParse({
    ...result.data,
    ...meta,
    files,
  })

  if (!parsed.success) {
    console.error(parsed.error.message)
    return null
  }

  return parsed.data
}

async function getFileContent(file: z.infer<typeof registryItemFileSchema>) {
  const key = file.path.replaceAll('registry/new-york-v4/blocks/', '').replaceAll('/', ':')
  const raw = await useStorage<string | Uint8Array>('assets:blocks').getItem(key)

  // const project = new Project({
  //   compilerOptions: {},
  // })

  // const tempFile = await createTempSourceFile(file.path)
  // const sourceFile = project.createSourceFile(tempFile, raw, {
  //   scriptKind: ScriptKind.TSX,
  // })

  // Remove meta variables.
  // removeVariable(sourceFile, "iframeHeight")
  // removeVariable(sourceFile, "containerClassName")
  // removeVariable(sourceFile, "description")

  let code = typeof raw === 'string' ? raw : new TextDecoder().decode(raw ?? undefined)// sourceFile.getFullText()
  // Fix imports.
  code = fixImport(code)

  return code
}

function getFileTarget(file: z.infer<typeof registryItemFileSchema>) {
  let target = file.target

  if (!target || target === '') {
    const fileName = file.path.split('/').pop()
    if (
      file.type === 'registry:block'
      || file.type === 'registry:component'
      || file.type === 'registry:example'
    ) {
      target = `components/${fileName}`
    }

    if (file.type === 'registry:ui') {
      target = `components/ui/${fileName}`
    }

    if (file.type === 'registry:hook') {
      target = `hooks/${fileName}`
    }

    if (file.type === 'registry:lib') {
      target = `lib/${fileName}`
    }
  }

  return target ?? ''
}

async function createTempSourceFile(filename: string) {
  const dir = await fs.mkdtemp(path.join(tmpdir(), 'shadcn-'))
  return path.join(dir, filename)
}

function fixFilePaths(files: z.infer<typeof registryItemSchema>['files']) {
  if (!files) {
    return []
  }

  // Resolve all paths relative to the first file's directory.
  const firstFilePath = files[0].path
  const firstFilePathDir = path.dirname(firstFilePath)

  return files.map((file) => {
    return {
      ...file,
      path: path.relative(firstFilePathDir, file.path),
      target: getFileTarget(file),
    }
  })
}

export interface FileTree {
  name: string
  path?: string
  children?: FileTree[]
}

export function createFileTreeForRegistryItemFiles(
  files: Array<{ path: string, target?: string }>,
) {
  const root: FileTree[] = []

  for (const file of files) {
    const path = file.target ?? file.path
    const parts = path.split('/')
    let currentLevel = root

    for (let i = 0; i < parts.length; i++) {
      const part = parts[i]
      const isFile = i === parts.length - 1
      const existingNode = currentLevel.find(node => node.name === part)

      if (existingNode) {
        if (isFile) {
          // Update existing file node with full path
          existingNode.path = path
        }
        else {
          // Move to next level in the tree
          currentLevel = existingNode.children!
        }
      }
      else {
        const newNode: FileTree = isFile
          ? { name: part, path }
          : { name: part, children: [] }

        currentLevel.push(newNode)

        if (!isFile) {
          currentLevel = newNode.children!
        }
      }
    }
  }

  return root
}
